<?php
/**
 * Arquivo de Defini��o da Classe Modelo
 * LEIA!
 * 1) classe Modelo (Model ou o M do MVC)
 * � a representa��o espec�fica da INFORMA��O em que a aplica��o opera. Por exemplo,
 * aluno, professor e turma fazem parte do dom�nio de um sistema acad�mico. As
 * informa��es espec�ficas de aluno, professor e turma s�o dispostas em Modelos.
 * � comum haver confus�o pensando que Modelo � um outro nome para a CAMADA DE DOM�NIO.
 * A L�GICA DE DOM�NIO adiciona sentido aos dados (por exemplo, calcular se hoje �
 * anivers�rio do usu�rio, ou calcular o total de impostos e fretes sobre um determinado
 * carrinho de compras).
 *
 * 2) Modelo e Persist�ncia
 * Muitas aplica��es usam um mecanismo de armazenamento persistente (como banco de dados)
 * para armazenar dados. MVC n�o cita especificamente a camada para acesso aos dados,
 * porque subentende-se que estes m�todos estariam encapsulados pelo Modelo.
 *
 * 3) Problemas com a Persist�ncia
 * H� ainda a necessidade de verificar a integridade dos dados nos relacionamentos, bem
 * como o mapeamento objeto relacional. Outros frameworks tem utilizado solu��es baseadas
 * em XML, YML e etc... Nos optamos por deixar que o usu�rio cuide disso. O motivo �
 * simples: Se formos tratar desse assunto, a curva de aprendizagem sobe muito, pois o
 * programador ter� que aprender v�rias palavras chave do framework.
 *
 *
 * 4) Uso
 * Todos os modelos que voc� precisar para a sua aplica��o, voc� criar� a partir dessa
 * classe, de forma que uma Modelo Professor dever�r extender a classe modelo. � importante
 * que todos os atributos sejam protected. Dessa forma eles n�o ser�o lidos diretamente.
 * A classe Modelo cria gets e sets m�gicos, e caso voc� precise implementar alguma regra
 * no model para get e set basta criar uma fun��o contendo get/set+nome do atributo. Por
 * exemplo, para o atributo $nome, crie fun��es getnome ou setnome($valor)...
 * Outro ponto importante � que a classe n�o reconhecer� como atributo do modelo um campo
 * iniciado por underline "_nome".
 */
 /**
 * @author Alan em 20/08/2008
 * @version 1.0
 * @package Persistencia
 *
 */
abstract class Modelo {
	/**
	 * Variavel que guarda um Objeto do Tipo Conexao
	 * @access protected
	 * @var Conexao Objeto Conexao
	 */
	protected $_conexao = null;
	/**
	 * Variavel que guarda um Array contendo todos os atributos da classe
	 * @access protected
	 * @var array String
	 */
	protected $_atributos = null;
	/**
	 * Variavel que guarda um Array contendo todos os m�todos da classe
	 * @access protected
	 * @var array String
	 */
	protected $_metodos = null;
	/**
	 * Construtor da Classe
	 */
	public final function __construct(Conexao $conexao){
		$this->_conexao		= $conexao;
		$this->_atributos 	= $this->_getArrayAtributos();
		$this->_metodos 	= $this->_getArrayMetodos();
	}
	/**
	 * @access protected
	 * @return array string - Array contendo todos os atributos da classe (n�o iniciados por _(underline)
	 */
	private final function _getArrayAtributos() {
		$atrs = array();
		$atb = get_object_vars($this);
		foreach ($atb as $nome => $valor) {
			if (substr($nome, 0, 1) != "_") {
				$atrs[] = $nome;
			}
		}
		return $atrs;
	}

        /**
	 * @access private
	 * @return array string - Array contendo todos os atributos da classe (n�o iniciados por _(underline)
	 */
	protected final function _getArrayAtributosPai($classe) {
		$obj = new $classe($this->_conexao);
                $atb = $obj->_getAtributos();
		return $atb;
	}


	/**
	 * @access protected
	 * @return array string - Array contendo todos os m�todos da classe (n�o iniciados por _(underline)
	 */
	private final function _getArrayMetodos() {
		$mtds = array();
		foreach (get_class_methods(get_class($this)) as $metodo) {
			// Verifica se o atributo come�a com _ (Nesse caso desprezamos por considera-lo uma
			// propriedade interna da classe e n�o um campo do banco de dados
			if (substr($metodo, 0, 1) != "_") {
				$mtds[] = $metodo;
			}
		}
		return $mtds;
	}
	/**
	 * M�todo M�gico __set.
	 * Permite alterar atributos da classe.
	 * Sempre que houver um m�todo set[NomeAtributo], esse m�todo ser� chamado.
	 * Note que para chamar o m�todo basta instanciar uma classe e tentar alterar o
	 * valor do atributo diretamente.
	 * Ex:
	 * $a = new Classe();
	 * $a->Atributo = 10;
	 * Aqui a classe ir� verificar se existe um m�todo setAtributo.
	 * Se houver, esse m�todo ser� chamado e o valor ser� passado ao m�todo.
	 * Caso contr�rio, o Atributo ser� modificado diretamente.
	 * @param string Nome do Atributo
	 * @param string Valor do Atributo
	 */
	public final function __set($atributo, $valor) {
		// Crio o nome do M�todo que estamos buscando.
		$metodo = "set". ucfirst($atributo);
		// Verifica se o m�todo existe
		if (in_array($metodo, $this->_metodos)){
			//Se existir utiliza o m�todo para setar as vari�veis.
			$this->$metodo($valor);
		} elseif (in_array($atributo, $this->_atributos)) {
			$this->$atributo = $valor;
			return true;
		} else {
			// TODO: Deve-se Lan�ar uma exce��o aqui
			// Algo como return new TErroFatal("04", get_class($this) ,"O atributo << " . $atributo . " >> n&atilde;o foi encontrado na classe (ou nas superclasses de " . get_class($this),"Verifique se voc&ecirc; escreveu corretamente o nome do atributo da classe.");
			die("[set]Atributo $atributo n�o encontrado!");
		}
	}
	/**
	 * M�todo M�gico __get.
	 * Permite ler atributos da classe.
	 * Sempre que houver um m�todo get[NomeAtributo], esse m�todo ser� chamado.
	 * Note que para chamar o m�todo basta instanciar uma classe e tentar ler o
	 * valor do atributo diretamente.
	 * Ex:
	 * $a = new Classe();
	 * echo $a->Atributo;
	 * Aqui a classe ir� verificar se existe um m�todo getAtributo.
	 * Se houver, esse m�todo ser� chamado e o valor retornado ser� passado pelo m�todo.
	 * Caso contr�rio, o Atributo ser� retornado diretamente.
	 * @param string Nome do Atributo
	 * @return mixed O valor armazenado no atributo
	 */
	public final function __get($atributo) {
		// Crio o nome do M�todo que estamos buscando.

		$metodo = "get". ucfirst($atributo);
		// Verifica se o m�todo existe
		if (in_array($metodo, $this->_metodos)){
			//Se existir utiliza o m�todo para pegar as vari�veis.
			return $this->$metodo();
		} elseif (in_array($atributo, $this->_atributos)) {
			return $this->$atributo;
		} else {
			// TODO: Deve-se Lan�ar uma exce��o aqui
			// Algo como return new TErroFatal("04", get_class($this) ,"O atributo << " . $atributo . " >> n&atilde;o foi encontrado na classe (ou nas superclasses de " . get_class($this),"Verifique se voc&ecirc; escreveu corretamente o nome do atributo da classe.");
			die("[get]Atributo $atributo n�o encontrado!");
		}
	}

	/**
	 * M�todo _getValores.
	 * Retorna um array com todos os valores para cada Atributo da Classe.
	 * Note que atributos iniciados por _ [underline] n�o s�o levados em
	 * considera��o
	 * @return array string Contendo os valores de todos os atributos do objeto
	 */
	public function _getValores(){
		$atrs = $this->_getAtributos();
		foreach($atrs as $atr){
			$ret[] = $this->__get($atr);
		}
		return $ret;
	}
	/**
	 * M�todo _getAtributos
	 * Retorna um array com todos os atributos da Classe.
	 * Note que atributos iniciados por _ [underline] n�o s�o levados em
	 * considera��o
	 * @return array string Contendo o nome de todos os atributos do objeto
	 */
	public function _getAtributos(){
            $pai = $this->getPai();
            if($pai == "Modelo"){
                return $this->_atributos;
            } else {
                $arr = $this->_atributos;
                $objPai = new $pai($this->_conexao);
                $arrPai = $objPai->_getAtributos();
                $arr = array_diff($arr, $arrPai);
                return $arr;
            }
		
	}
	/**
	 * M�todo _getArray
	 * Retorna um array com todos os atributos e valores da Classe.
	 * @return array string Contendo o nome de todos os atributos do objeto
	 */
	public function _getArray(){
		$atb = get_object_vars($this);
		foreach ($atb as $nome => $valor) {
			if (substr($nome, 0, 1) != "_") {
				$atrs[$nome] = $valor;
			}
		}
		return $atrs;
	}
	/**
	 * M�todo _setAtributos
	 * Preenche o modelo utilizando um array. Verifica se as chaves do array
	 * enviado s�o atributos da classe.
	 * @return boolean verdadeiro se foi poss�vel preencher os atributos.
	 */
	public function _setArray($array){
		foreach ($array as $atr => $valor) {
			if (substr($atr, 0, 1) != "_") {
				if(in_array($atr,$this->_atributos)){
					$this->__set($atr, $valor);
				} else {
					// TODO: Deve-se Lan�ar uma exce��o aqui
					// Algo como return new TErroFatal("04", get_class($this) ,"O atributo << " . $atributo . " >> n&atilde;o foi encontrado na classe (ou nas superclasses de " . get_class($this),"Verifique se voc&ecirc; escreveu corretamente o nome do atributo da classe.");
					die("[set]Array $atr n�o encontrado no objeto " . get_class($this));
					return false;
				}
			}
		}
		return true;
	}
	/**
	 * Essa fun��o dever� ser sobrescrita quando a chave prim�ria
	 * n�o estiver declarada como primeiro atributo, ou quando a chave
	 * for composta. Nesse caso, o retorno sera o nome dos campos
	 * separados por virgula.
	 */
	public function _getCampoChavePrimaria(){
		$atrs	= $this->_getAtributos();
		return $atrs[0];
	}
	/**
	 * Essa fun��o retorna o valor da chave prim�ria
	 * @return mixed;
	 */
	public function _getValorChavePrimaria(){
		$cp = $this->_getCampoChavePrimaria();
		$ret = $this->__get($cp);
		return  $ret;
	}
	/**
	 * Essa fun��o dever� ser sobrescrita quando o nome da tabela for
	 * diferente do nome da classe. Nesse caso, devera retornar o nome da
	 * tabela do banco de dados.
	 */
	public function _getEntidade(){
		$tabela = get_class($this);
		return strtolower($tabela);
	}
	/**
	 * Essa fun��o salva os dados no Banco de Dados.
	 * Caso o Id seja informado, os dados ser�o atualizados.
	 * Se o Id n�o for informado, o sistema far� a inclus�o de um novo registro.
	 */
    public function salvar(){
    	// Pega o campo chave Prim�ria
    	$campoChave = $this->_getCampoChavePrimaria();
    	$valorChave = $this->_getValorChavePrimaria();
    	if($valorChave == null){

    		$this->insert();
    	} else{
            // Precisamos verificar se j� existe algum registro com a chave prim�ria
            if($this->existe()){
                $this->update();
            } else {
                $this->insert();
            }
        }
    }
	/**
	 * Essa fun��o retira a �ltima v�rgula de uma string
	 */
    protected function retiraUltVirgula($sql){
    	return $sql = substr($sql, 0,strlen($sql)-2);
    }
	/**
	 * Essa fun��o retorna uma consulta a Tabela no Banco de Dados
	 */
    public function SQLselect(){
		$arr = $this->_getAtributos();
		$cp  = $this->_getCampoChavePrimaria();
        $sqlAtb = '';
		foreach($arr as $atb){
			$sqlAtb .= "$atb, ";
		}
		$sqlAtb = $this->retiraUltVirgula($sqlAtb);
		$tbl = $this->_getEntidade();
		$sql = "SELECT $sqlAtb FROM $tbl ";
		$sql.= "WHERE $cp = :$cp;";
		return $sql;
    }
	/**
	 * Essa fun��o retorna um Comando SQL para inser��o
	 */
    public function SQLinsert(){
		$arr = $this->_getAtributos();
        $sqlAtb = "";
        $sqlVal = "";
		foreach($arr as $atb){
			$sqlAtb .= "$atb, ";
			$sqlVal .= ":$atb, ";
		}
		$tbl = $this->_getEntidade();
		$sql = "INSERT INTO $tbl( ";
		$sql.= $this->retiraUltVirgula($sqlAtb) . ") ";
		$sql.= "VALUES (" . $this->retiraUltVirgula($sqlVal) . ") ";
		return $sql;
    }
	/**
	 * Essa fun��o retorna um Comando SQL para atualiza��o dos dados no banco
	 */
    public function SQLupdate(){
    	// Pega o Array de Atributos da Classe
		$arr = $this->_getAtributos();
		// Pega o nome da Tabela do Banco de Dados
		$tbl = $this->_getEntidade();
		$cp  = $this->_getCampoChavePrimaria();
		$sql = "UPDATE $tbl SET ";
		foreach($arr as $atb){
			$sql .= "$atb = :$atb, ";
		}
		// Retiro a �ltima virgula do SQL
		$sql = $this->retiraUltVirgula($sql) . " ";
		// Acrescento a cl�usula WHERE
		$sql.= "WHERE $cp = :$cp;";
		return $sql;
    }
	/**
	 * Essa fun��o retorna um Comando SQL para exclus�o de um registro no Banco
	 */
    public function SQLdelete(){
		// Pega o nome da Tabela do Banco de Dados
		$tbl = $this->_getEntidade();
		$cp  = $this->_getCampoChavePrimaria();
		$sql = "DELETE FROM $tbl ";
		$sql.= "WHERE $cp = :$cp;";
		return $sql;
    }
	/**
	 * Essa fun��o seleciona um registro no Banco e populaciona os campos do objeto
	 */
    public function select($id=0){
    	$sql 	= $this->SQLselect();
		$con	= new ConsultaPreparada($this->_conexao, $sql);
		$cp		= $this->_getCampoChavePrimaria();
		$vp		= $id==0?$this->_getValorChavePrimaria():$id;
		$ar[$cp]=$vp;
		$this->liga($con, $ar);
		foreach($con->getResultados()as $linha){
			foreach($linha as $campo=>$valor){
				$this->__set($campo,$valor);
			}
		}
    }

    public function existe($id=0){
    	$sql 	= $this->SQLselect();
        //echo $sql;
        $con	= new ConsultaPreparada($this->_conexao, $sql);
        $cp	= $this->_getCampoChavePrimaria();
        $vp	= $id==0?$this->_getValorChavePrimaria():$id;
        $ar[$cp]=$vp;
        $this->liga($con, $ar);
        if($con->getQtdeLinhas()){
            return true;
        } else {
            return false;
        }
    }




    public function insert(){
    	$sql 	= $this->SQLinsert();
		$comm	= new ComandoPreparado($this->_conexao, $sql);
		$cp 	= $this->_getCampoChavePrimaria();
		$this->liga($comm);
		// Registros Afetados
		$ra = $comm->executa();
		$this->__set($cp ,$comm->getUltimoId());
                
		return $ra;

    }
    public function update(){
    	$sql 	= $this->SQLupdate();
		$comm	= new ComandoPreparado($this->_conexao, $sql);
		$this->liga($comm);
		return $comm->executa();
    }
    public function delete(){
    	$sql 	= $this->SQLdelete();
		$comm	= new ComandoPreparado($this->_conexao, $sql);
		$cp		= $this->_getCampoChavePrimaria();
		$vp		= $this->_getValorChavePrimaria();
		$ar[$cp]=$vp;
		$this->liga($comm, $ar);
		return $comm->executa();
    }

    // Recebe obrigatoriamente um Array Associativo.
    public function liga(iPreparado $comando, $arr = null) {
        if($arr == null){
            // Construir o array associativo
            $arrV = $this->_getAtributos();
            foreach($arrV as $atb) {
                $val = $this->__get($atb);
                $val = empty($val)?null:$val;
                $arr[$atb] = $val;
            }
        }
    	foreach($arr as $atb=>$val) {
    		$val = empty($val)?null:$val;
    		$comando->liga($atb, $val);
    	}
    }

	public function getConexao(){
		return $this->_conexao;
	}

	public function get(){
		$ret = false;
		foreach($this->_atributos as $atributo){
			if (isset($_GET[$atributo])){
				$ret = true;
                                $this->__set($atributo, $_GET[$atributo]);                                
			}
		}
		return $ret;
	}
	public function post(){
		$ret = false;
		foreach($this->_atributos as $atributo){
			if (isset($_POST[$atributo])){
				$ret = true;
                                $this->__set($atributo, $_POST[$atributo]);
			}
		}
		return $ret;
	}
	public function request(){
		$ret = false;
		foreach($this->_atributos as $atributo){
			if (isset($_REQUEST[$atributo])){
				$ret = true;
				$this->__set($atributo, $_REQUEST[$atributo]);
			}
		}
		return $ret;
	}
        public function getPai(){
            $nomeClasse = get_class($this);
            return get_parent_class( $nomeClasse );
        }
}
?>